#!/usr/bin/perl
use strict;
use warnings;

use lib '../lib';
use lib '../extlib';
use ChessLike::Board;
use ChessLike::GamePiece;

use List::MoreUtils qw( true firstidx );

use Test::Exception;
use Test::More 'no_plan';

########################
### Basic Operations ###
########################
print "\n*** Testing basic operations ***\n";

# Create a brand new gameboard with custom dimensions
my $board = ChessLike::Board->new( 'x-tiles' => 10, 'y-tiles' => 4 );

is( ref $board,        'ChessLike::Board', '[ref] ChessLike::Board' );
is( $board->x_tiles(), 10,                 '[new] custom dimensions: x' );
is( $board->y_tiles(), 4,                  '[new] custom dimensions: y' );

# Test dimension boundaries
dies_ok { $board->new( 'x-tiles' => 0 ) } '[new] dimension boundaries: 0';
dies_ok { $board->new( 'y-tiles' => 'four' ) }
'[new] dimension boundaries: alpha';

# Re-create the chessboard, this time using default settings
$board = ChessLike::Board->new();

is( ref $board,        'ChessLike::Board', '[ref] ChessLike::Board' );
is( $board->x_tiles(), 8,                  '[new] default dimensions: x' );
is( $board->y_tiles(), 8,                  '[new] default dimensions: y' );

# Test convert()
is( $board->convert( 18, 'tile' ), 'c3', '[convert] id to tile' );
is( $board->convert( 'c3', 'id' ), 18, '[convert] tile to id' );
is( $board->convert( 33, 'xy' ), '2,5', '[convert] id to xy' );
is( $board->convert( '3,3', 'id' ), 18, '[convert] xy to id' );
is( $board->convert( 'c3', 'xy' ), '3,3', '[convert] tile to xy' );
is( $board->convert( '8, 8', 'tile' ), 'h8',
   '[convert] xy (+ whitespace) to tile' );
is( $board->convert( '8, 8', 'xy' ), '8,8',
   '[convert] xy (+ whitespace) to xy' );
is( $board->convert( '15', 'id' ), 15, '[convert] id to id' );
is( $board->convert( 'v12', 'tile' ), 'v12',
   '[convert] tile (multiple digits) to tile' );
dies_ok { $board->convert( undef, 'tile' ) } '[convert] undefined input';
dies_ok { $board->convert( '15' ) } '[convert] missing argument';
dies_ok { $board->convert( '9tynine', 'tile' ) } '[convert] bad input format';
dies_ok { $board->convert( 'a4', 'idunno' ) } '[convert] bad conversion format';

# Test add() with bad input
dies_ok {
   $board->add(
      'a1',
      'gamepiece' => 'Pawn',
      'direction' => 'n',
      'team'      => 0,
      'graphic'   => 'anything',
   );
}
'[add] bad input: gamepiece as string';

# Check basic add routine
my $pawn1 = ChessLike::GamePiece->new();
$board->add(
   'a1',
   'gamepiece' => $pawn1,
   'direction' => 'n',
   'team'      => 0,
   'graphic'   => 'anything',
);

## a1: Pawn1

my ( $graphics_ref, $teams_ref, $gamepieces_ref, $directions_ref )
  = $board->get_tile_contents( 0, 'graphics', 'teams', 'gamepieces',
   'directions' );

is( @{$gamepieces_ref}, 1, '[get_tile_contents] gamepieces array size' );
is( @{$teams_ref},      1, '[get_tile_contents] teams array size' );
is( @{$directions_ref}, 1, '[get_tile_contents] directions array size' );
is( @{$graphics_ref},   1, '[get_tile_contents] graphics array size' );

is( $gamepieces_ref->[0], $pawn1,
   '[get_tile_contents] gamepieces array contents' );
is( $teams_ref->[0], 0, '[get_tile_contents] teams array contents' );
is( $directions_ref->[0], 'n',
   '[get_tile_contents] directions array contents' );
is( $graphics_ref->[0], 'anything',
   '[get_tile_contents] graphics array contents' );

# Try to change the Board instance through the gamepieces ArrayRef
my $stupid_pawn = ChessLike::GamePiece->new();
$gamepieces_ref->[0] = $stupid_pawn;
$gamepieces_ref = $board->get_tile_contents( 0, 'gamepieces' );
is( $gamepieces_ref->[0], $pawn1, '[internals] protected tile contents' );
    
# This next line is to make sure that a failure in the previous test doesn't
# cause downstream tests to fail.
$gamepieces_ref->[0] = $pawn1;

# Add a flying unit to the same tile
my $flying_pawn1 = ChessLike::GamePiece->new( 'is-flying' => 1, );
$board->add(
   'a1',
   'gamepiece'     => $flying_pawn1,
   'direction' => 'w',
   'team'      => 2,
   'graphic'   => 'flying-anything',
);

## a1: Pawn-1, Flying-Pawn-1

( $gamepieces_ref, $teams_ref )
    = $board->get_tile_contents( 0, 'gamepieces', 'teams' );

is( @{$gamepieces_ref}, 2, '[add] gamepieces array size' );
is( @{$teams_ref},      2, '[add] teams array size' );

is( $gamepieces_ref->[0], $pawn1,
   '[add] flying unit, checking order of addition' );
is( $teams_ref->[0], 0,
   '[add] flying unit, checking order of addition' );
is( $gamepieces_ref->[1], $flying_pawn1,
   '[add] flying unit, checking order of addition' );
is( $teams_ref->[1], 2,
   '[add] flying unit, checking order of addition' );

# Add a second pawn to the same tile
my $pawn2 = ChessLike::GamePiece->new();
$board->add(
   'a1',
   'gamepiece' => $pawn2,
   'direction' => 's',
   'team'      => 1,
   'graphic'   => 'anything',
);

## a1: Pawn-2, Pawn-1, Flying-Pawn-1

$gamepieces_ref = $board->get_tile_contents( 0, 'gamepieces' );

is( $gamepieces_ref->[0], $pawn2,
   '[add] ground unit, checking order of addition' );
is( $gamepieces_ref->[1], $pawn1,
   '[add] ground unit, checking order of addition' );
is( $gamepieces_ref->[2], $flying_pawn1,
   '[add] ground unit, checking order of addition' );

# Try removing a unit
$board->remove( 'a1', 1 );

## a1: Pawn-2, Flying-Pawn-1

$gamepieces_ref = $board->get_tile_contents( 0, 'gamepieces' );

is( $gamepieces_ref->[0], $pawn2,
   '[remove] checking remaining tile contents' );
is( $gamepieces_ref->[1], $flying_pawn1,
   '[remove] checking remaining tile contents' );

# Try adding a new unit to a different tile using cartesian notation
my $flying_pawn2 = ChessLike::GamePiece->new( 'is-flying' => 1, );
$board->add(
   '2,2',
   'gamepiece'     => $flying_pawn2,
   'direction' => 'e',
   'team'      => 2,
   'graphic'   => 'flying-anything',
);

## a1: Pawn-2, Flying-Pawn-1
## b2: Flying-Pawn-2

# Add another unit to the new tile, with additional, bogus parameters
my $pawn3 = ChessLike::GamePiece->new();
$board->add(
   'b2',
   'gamepiece' => $pawn3,
   'direction' => 's',
   'team'      => 0,
   'graphic'   => 'anything',
   'tonedef'   => 1,
   'smells-of' => 'herring guts',
);

## a1: Pawn-2, Flying-Pawn-1
## b2: Pawn-3, Flying-Pawn-2

# Check contents of new tile directly, using tile indices
my ( $direction, $team, $gamepiece, $graphic )
  = $board->get_tile_contents_for_index( 'b2', 0, 'direction', 'team',
   'gamepiece', 'graphic' );

is( $gamepiece, $pawn3,     '[get_tile_contents_for_index] gamepiece' );
is( $team,      0,   '[get_tile_contents_for_index] team' );
is( $direction, 's',        '[get_tile_contents_for_index] direction' );
is( $graphic,   'anything', '[get_tile_contents_for_index] graphic' );

# Check the next index, except this time using cartesian notation
$team = $board->get_tile_contents_for_index( '2,2', 1, 'team' );
is( $team, 2, '[get_tile_contents_for_index] team' );

# Try moving our flier to the original tile using a mix of notations
$board->move( 'b2', 1, '1,1' );

## a1: Pawn-2, Flying-Pawn-1, Flying-Pawn-2
## b2: Pawn-3

$gamepieces_ref = $board->get_tile_contents( 'b2', 'gamepieces' );

is( @{$gamepieces_ref}, 1, '[move] checking starting tile array size' );
is( $gamepieces_ref->[0], $pawn3, '[move] checking starting tile contents' );

$gamepieces_ref = $board->get_tile_contents( '1,1', 'gamepieces' );

is( @{$gamepieces_ref}, 3, '[move] checking destination tile array size' );
is( $gamepieces_ref->[0], $pawn2,
   '[move] checking destination tile contents' );
is( $gamepieces_ref->[1], $flying_pawn1,
   '[move] checking destination tile contents' );
is( $gamepieces_ref->[2], $flying_pawn2,
   '[move] checking destination tile contents' );

( $direction, $team, $gamepiece, $graphic )
  = $board->get_tile_contents_for_index( 'a1', 2, 'direction', 'team',
   'gamepiece', 'graphic' );

is( $gamepiece, $flying_pawn2, '[move] checking state of moved gamepiece' );
is( $team,      2,    '[move] checking state of moved gamepiece' );
is( $direction, 'e',           '[move] checking state of moved gamepiece' );
is( $graphic, 'flying-anything', '[move] checking state of moved gamepiece' );

#####################
### Configuration ###
#####################
print "\n*** Testing configuration ***\n";

$board->configure( 'a1', 2,
   'direction' => 's',
   'graphic'   => 'flying-everything', );

( $direction, $team, $gamepiece, $graphic )
  = $board->get_tile_contents_for_index( 'a1', 2, 'direction', 'team',
   'gamepiece', 'graphic' );

is( $gamepiece, $flying_pawn2, '[configure] gamepiece preserved' );
is( $team,      2,    '[configure] team preserved' );
is( $direction, 's',           '[configure] direction changed' );
is( $graphic, 'flying-everything', '[configure] graphic changed' );

$board->configure( 'a1', 2,
   'gamepiece' => $flying_pawn1,
   'team'      => 1,
   'graphic'   => 'flying-anything', );

( $direction, $team, $gamepiece, $graphic )
  = $board->get_tile_contents_for_index( 'a1', 2, 'direction', 'team',
   'gamepiece', 'graphic' );

is( $gamepiece, $flying_pawn1, '[configure] gamepiece changed' );
is( $team,      1,    '[configure] team changed' );
is( $direction, 's',           '[configure] direction preserved' );
is( $graphic, 'flying-anything', '[configure] graphic changed' );

#############################
### Legal/Illegal Actions ###
#############################
print "\n*** Testing legal/illegal actions ***\n";

#################
#   #   #   #   #
#################
#   #   # pf#   #
#################
#   # A #   #   #
#################
# F #   #   #   #
#################
## Uppercase = Player1
## Lowercase = Player2
## P = Pawn
## A = Artillery
## F = Flying Bishop

$board = ChessLike::Board->new( 'x-tiles' => 4, 'y-tiles' => 4 );
my $pawn = ChessLike::GamePiece->new(
   'name'         => 'Pawn',
   'move-vectors' => [ [ -1, 1, 1 ], [ 1, 1, 1 ] ],
);
my $artillery = ChessLike::GamePiece->new(
   'name'         => 'Artillery',
   'move-vectors' => [ [ -1, 1, 1 ], [ 0, 1, 1 ], [ 1, 1, 1 ],
                       [ -1, 0, 1 ],            , [ 1, 0, 1 ],
                       [ -1,-1, 1 ], [ 0,-1, 1 ], [ 1,-1, 1 ] ],
   'shot-vectors' => [ [ -1, 1, 2 ], [ 0, 1, 2 ], [ 1, 1, 2 ] ],
   'can-rotate'   => 1,
);
my $flying_bishop = ChessLike::GamePiece->new(
   'name'         => 'Flying Bishop',
   'move-vectors' => [ [ -1, 1, 99 ], [ 1, 1, 99 ],
                       [ -1,-1, 99 ], [ 1,-1, 99 ] ],
   'is-flying'   => 1,
);

$board->add(
   'a1',
   'gamepiece' => $flying_bishop,
   'direction' => 'n',
   'team'      => 0,
   'graphic'   => 'flying_bishop.gif',
);
$board->add(
   'b2',
   'gamepiece' => $artillery,
   'direction' => 'n',
   'team'      => 0,
   'graphic'   => 'artillery.gif',
);
$board->add(
   'c3',
   'gamepiece' => $pawn,
   'direction' => 's',
   'team'      => 1,
   'graphic'   => 'pawn.gif',
);
$board->add(
   'c3',
   'gamepiece' => $flying_bishop,
   'direction' => 's',
   'team'      => 1,
   'graphic'   => 'flying_bishop',
);

# Now we check every single action
my ( $count, @legal_actions, @illegal_actions );

## LEGAL
## F: a1-move-b2
## F: a1-capture-c3-index1
@legal_actions = $board->legal_actions( 'a1', 0 );
is( @legal_actions, 2, '[legal_actions] a1.0: array size' );

foreach my $action ( 'a1-b2', 'a1+c3.1', ) {
	$count = true { $_->to_string() eq $action } @legal_actions;
	is( $count, 1, "[legal_actions] $action" );
}

## ILLEGAL
## F: a1-move-d4
@illegal_actions = $board->illegal_actions( 'a1', 0 );
is( @illegal_actions, 1, '[illegal_actions] a1.0: array size' );

foreach my $action ( 'a1-d4', ) {
   $count = true { $_->to_string() eq $action } @illegal_actions;
   is( $count, 1, "[illegal_actions] $action" );
}

## LEGAL
# A: b2-rotation
# A: b2-move-a3
# A: b2-move-b3
# A: b2-capture-c3-index0
# A: b2-move-a2
# A: b2-move-c2
# A: b2-move-a1
# A: b2-move-b1
# A: b2-move-c1
# A: b2-shoot-c3-index0
# A: b2-shoot-c3-index1
@legal_actions = $board->legal_actions( 'b2', 0 );
is( @legal_actions, 11, '[legal_actions] b2.0: array size' );

foreach my $action ( 'b2>', 'b2-a3',
  'b2-b3', 'b2+c3', 'b2-a2',
  'b2-c2', 'b2-a1', 'b2-b1',
  'b2-c1', 'b2*c3', 'b2*c3.1', ) {
    $count = true { $_->to_string() eq $action } @legal_actions;
    is( $count, 1, "[legal_actions] $action" );
}

## ILLEGAL
# A: b2-shoot-a3
# A: b2-shoot-b3
# A: b2-shoot-b4
# A: b2-shoot-d4
@illegal_actions = $board->illegal_actions( 'b2', 0 );
is( @illegal_actions, 4, '[illegal_actions] b2.0: array size' );

foreach my $action ( 'b2*a3', 'b2*b3', 'b2*b4', 'b2*d4' ) {
   $count = true { $_->to_string() eq $action } @illegal_actions;
   is( $count, 1, "[illegal_actions] $action" );
}

## LEGAL
# p: c3-capture-b2-index0
# p: c3-move-d2
@legal_actions = $board->legal_actions( 'c3', 0 );
is( @legal_actions, 2, '[legal_actions] c3.0: array size' );

foreach my $action ( 'c3+b2', 'c3-d2', ) {
    $count = true { $_->to_string() eq $action } @legal_actions;
    is( $count, 1, "[legal_actions] $action" );
}

## ILLEGAL
# p: nothing!
@illegal_actions = $board->illegal_actions( 'c3', 0 );
is( @illegal_actions, 0, '[illegal_actions] c3.0: array size' );

## LEGAL
# f: c3-move-b4
# f: c3-move-d4
# f: c3-move-b2
# f: c3-capture-b2-index0
# f: c3-move-d2
# f: c3-capture-a1-index0
@legal_actions = $board->legal_actions( 'c3', 1 );
is( @legal_actions, 6, '[legal_actions] c3.1: array size' );

foreach my $action ( 'c3.1-b4', 'c3.1-d4',
  'c3.1-b2', 'c3.1+b2', 'c3.1-d2', 'c3.1+a1' ) {
    $count = true { $_->to_string() eq $action } @legal_actions;
    is( $count, 1, "[legal_actions] $action" );
}

## ILLEGAL
# f: nothing!
@illegal_actions = $board->illegal_actions( 'c3', 1 );
is( @illegal_actions, 0, '[illegal_actions] c3.1: array size' );


#######################
### Movement Fields ###
#######################
print "\n*** Testing movement fields ***\n";

#########################
#   #   #   #   #   #   #
#########################
#   #   #   #   #   #   #
#########################
#   #   # F1#   #^b2#   #
#########################
#   #   #^B1#   #   #   #
#########################
#   #   #   #   #   #   #
#########################
#   #   #   #   #^b1#   #
#########################
#<B2# F2#   #   # f1#   #
#########################
## Uppercase = Player1
## Lowercase = Player2
## B = Base
## F = Flying Queen

$board = ChessLike::Board->new( 'x-tiles' => 6, 'y-tiles' => 7 );
my $base = ChessLike::GamePiece->new(
   'name'         => 'Base',
   'move-vectors' => [ [ -1, 1, 1 ], [ 0, 1, 1 ], [ 1, 1, 1 ],
                       [ -1, 0, 1 ],              [ 1, 0, 1 ],
                       [ -1,-1, 1 ], [ 0,-1, 1 ], [ 1,-1, 1 ] ],
   'can-rotate'   => 1,
   'proxy-move-field'   => [ 3, 4, 2, 2 ],
   'carries-units' => 'Flying Queen',
);
my $flying_queen = ChessLike::GamePiece->new(
   'name'         => 'Flying Queen',
   'move-vectors' => [ [ -1, 1, 99 ], [ 0, 1, 99 ], [ 1, 1, 99 ],
                       [ -1, 0, 99 ],               [ 1, 0, 99 ],
                       [ -1,-1, 99 ], [ 0,-1, 99 ], [ 1,-1, 99 ] ],
   'is-flying'    => 1,
   'requires-units'    => 'Base',
);

$board->add(
   'a2',
   'gamepiece' => $base,
   'direction' => 'w',
   'team'      => 0,
   'graphic'   => 'base.gif',
);
$board->move( 'a2', 0, 'a1' );  # To ensure that carrier movement is properly tracked
$board->add(
   'b1',
   'gamepiece' => $flying_queen,
   'direction' => 'n',
   'team'      => 0,
   'graphic'   => 'flying_queen.gif',
);
$board->add(
   'c4',
   'gamepiece' => $base,
   'direction' => 'n',
   'team'      => 0,
   'graphic'   => 'base.gif',
);
$board->add(
   'c5',
   'gamepiece' => $flying_queen,
   'direction' => 'n',
   'team'      => 0,
   'graphic'   => 'flying_queen.gif',
);

$board->add(
   'e2',
   'gamepiece' => $base,
   'direction' => 'n',
   'team'      => 1,
   'graphic'   => 'base.gif',
);
$board->add(
   'e5',
   'gamepiece' => $base,
   'direction' => 'n',
   'team'      => 1,
   'graphic'   => 'base.gif',
);
$board->add(
   'e1',
   'gamepiece' => $flying_queen,
   'direction' => 's',
   'team'      => 1,
   'graphic'   => 'flying_queen.gif',
);

## LEGAL
## F1: c5-move-b6
## F1: c5-move-c6
## F1: c5-move-d6
## F1: c5-move-b5
## F1: c5-move-d5
## F1: c5-move-b4
## F1: c5-move-c4
## F1: c5-move-d4
## F1: c5-move-c3

@legal_actions = $board->legal_actions( 'c5', 0 );
is( @legal_actions, 9, '[legal_actions] c5.0: array size' );

foreach my $action ( 'c5-b6', 'c5-c6',
  'c5-d6', 'c5-b5', 'c5-d5', 'c5-b4',
  'c5-c4', 'c5-d4', 'c5-c3' ) {
    $count = true { $_->to_string() eq $action } @legal_actions;
    is( $count, 1, "[legal_actions] $action" );
}

## LEGAL
## F2: b1-move-a2
## F2: b1-move-b2
## F2: b1-move-a1

@legal_actions = $board->legal_actions( 'b1', 0 );
is( @legal_actions, 3, '[legal_actions] b1.0: array size' );

foreach my $action ( 'b1-a2', 'b1-b2', 'b1-a1' ) {
    $count = true { $_->to_string() eq $action } @legal_actions;
    is( $count, 1, "[legal_actions] $action" );
}

## LEGAL
## f1: e1-move-d1
## f1: e1-move-f1
## f1: e1-move-d2
## f1: e1-move-e2
## f1: e1-move-f2
## f1: e1-move-e3
## f1: e1-move-e4
## f1: e1-move-e5
## f1: e1-move-e6
## f1: e1-move-e7

@legal_actions = $board->legal_actions( 'e1', 0 );
is( @legal_actions, 10, '[legal_actions] e1.0: array size' );

foreach my $action ( 'e1-d1', 'e1-f1',
  'e1-d2', 'e1-e2', 'e1-f2', 'e1-e3',
  'e1-e4', 'e1-e5', 'e1-e6', 'e1-e7' ) {
    $count = true { $_->to_string() eq $action } @legal_actions;
    is( $count, 1, "[legal_actions] $action" );
}

####################
### Linked Units ###
####################
print "\n*** Testing linked units ***\n";

#############
# b #fF # F #
#############
#BF # B #   #
#############
## Uppercase = Player1
## Lowercase = Player2
## B = Base
## F = Flying Queen

$board = ChessLike::Board->new( 'x-tiles' => 3, 'y-tiles' => 2 );
$base = ChessLike::GamePiece->new(
   'name'         => 'Base',
   'move-vectors' => [ [ -1, 1, 1 ], [ 0, 1, 1 ], [ 1, 1, 1 ],
                       [ -1, 0, 1 ],              [ 1, 0, 1 ],
                       [ -1,-1, 1 ], [ 0,-1, 1 ], [ 1,-1, 1 ] ],
   'can-rotate'   => 1,
   'proxy-move-field'   => [ 3, 4, 2, 2 ],
   'carries-units' => 'Flying Queen',
);
$flying_queen = ChessLike::GamePiece->new(
   'name'         => 'Flying Queen',
   'move-vectors' => [ [ -1, 1, 99 ], [ 0, 1, 99 ], [ 1, 1, 99 ],
                       [ -1, 0, 99 ],               [ 1, 0, 99 ],
                       [ -1,-1, 99 ], [ 0,-1, 99 ], [ 1,-1, 99 ] ],
   'is-flying'    => 1,
   'requires-units'    => 'Base',
);

$board->add(
   'a1',
   'gamepiece' => $base,
   'direction' => 'n',
   'team'      => 0,
   'graphic'   => 'base.gif',
);

$board->add(
   'b1',
   'gamepiece' => $base,
   'direction' => 'n',
   'team'      => 0,
   'graphic'   => 'base.gif',
);

$board->add(
   'a1',
   'gamepiece' => $flying_queen,
   'direction' => 'n',
   'team'      => 0,
   'graphic'   => 'base.gif',
);

$board->add(
   'b2',
   'gamepiece' => $flying_queen,
   'direction' => 'n',
   'team'      => 0,
   'graphic'   => 'base.gif',
);

$board->add(
   'c2',
   'gamepiece' => $flying_queen,
   'direction' => 'n',
   'team'      => 0,
   'graphic'   => 'base.gif',
);

$board->add(
   'a2',
   'gamepiece' => $base,
   'direction' => 'n',
   'team'      => 1,
   'graphic'   => 'base.gif',
);

$board->add(
   'b2',
   'gamepiece' => $flying_queen,
   'direction' => 'n',
   'team'      => 1,
   'graphic'   => 'base.gif',
);

## B:a1

@legal_actions = $board->legal_actions( 'a1', 0 );
my $first_action = $legal_actions[0];

my @links = $first_action->links();
my @tentative_links = $first_action->shared_links();

is( @links, 1, '[links] a1.0: array size' );
foreach my $link ( 'a1.1' ) {
    $count = true { $_ eq $link } @links;
    is( $count, 1, "[links] $link" );
}

is( @tentative_links, 1, '[tentative-links] a1.0: array size' );
foreach my $tentative_link ( 'b2' ) {
    $count = true { $_ eq $tentative_link } @tentative_links;
    is( $count, 1, "[shared-links] $tentative_link" );
}

## B:b1

@legal_actions = $board->legal_actions( 'b1', 0 );
$first_action = $legal_actions[0];

@links = $first_action->links();
@tentative_links = $first_action->shared_links();

is( @links, 1, '[links]b1.0: array size' );
foreach my $link ( 'c2' ) {
    $count = true { $_ eq $link } @links;
    is( $count, 1, "[links] $link" );
}

is( @tentative_links, 1, '[shared-links] b1.0: array size' );
foreach my $tentative_link ( 'b2' ) {
    $count = true { $_ eq $tentative_link } @tentative_links;
    is( $count, 1, "[shared-links] $tentative_link" );
}

## b:a2

@legal_actions = $board->legal_actions( 'a2', 0 );
$first_action = $legal_actions[0];

@links = $first_action->links();
@tentative_links = $first_action->shared_links();

is( @links, 1, '[links] a2.0: array size' );
foreach my $link ( 'b2.1' ) {
    $count = true { $_ eq $link } @links;
    is( $count, 1, "[links] $link" );
}

is( @tentative_links, 0, '[shared-links] a2.0: array size' );

#################
### Rotations ###
#################
print "\n*** Testing rotations and link destinations ***\n";

#################
#   # F # f # b #
#################
# f # B #   # f #
#################
# b #   #   #   #
#################
# A #   #   #   #
#################
## Uppercase = Player1
## Lowercase = Player2
## B = Base
## A = Artillery
## F = Flying Bishop

$board = ChessLike::Board->new( 'x-tiles' => 4, 'y-tiles' => 4 );
$base = ChessLike::GamePiece->new(
   'name'         => 'Base',
   'move-vectors' => [ [ -1, 1, 1 ], [ 0, 1, 1 ], [ 1, 1, 1 ],
                       [ -1, 0, 1 ],              [ 1, 0, 1 ],
                       [ -1,-1, 1 ], [ 0,-1, 1 ], [ 1,-1, 1 ] ],
   'can-rotate'   => 1,
   'proxy-move-field'   => [ 3, 4, 2, 2 ],
   'carries-units' => 'Flying Bishop',
);
$artillery = ChessLike::GamePiece->new(
   'name'         => 'Artillery',
   'move-vectors' => [ [ -1, 1, 1 ], [ 0, 1, 1 ], [ 1, 1, 1 ],
                       [ -1, 0, 1 ],            , [ 1, 0, 1 ],
                       [ -1,-1, 1 ], [ 0,-1, 1 ], [ 1,-1, 1 ] ],
   'shot-vectors' => [ [ -1, 1, 2 ], [ 0, 1, 2 ], [ 1, 1, 2 ] ],
   'can-rotate'   => 1,
);
$flying_bishop = ChessLike::GamePiece->new(
   'name'            => 'Flying Bishop',
   'move-vectors'    => [ [ -1, 1, 99 ], [ 1, 1, 99 ],
                          [ -1,-1, 99 ], [ 1,-1, 99 ] ],
   'is-flying'      => 1,
   'requires-units' => 'Base',
);

$board->add(
   'a1',
   'gamepiece' => $artillery,
   'direction' => 'n',
   'team'      => 0,
   'graphic'   => 'some.gif',
);

$board->add(
    'a2',
    'gamepiece' => $base,
    'direction'=> 's',
    'team' => 1,
    'graphic'   => 'some.gif',
);

$board->add(
    'a3',
    'gamepiece' => $flying_bishop,
    'direction'=> 's',
    'team' => 1,
    'graphic'   => 'some.gif',
);

$board->add(
    'b3',
    'gamepiece' => $base,
    'direction'=> 'n',
    'team' => 0,
    'graphic'   => 'some.gif',
);

$board->add(
    'd3',
    'gamepiece' => $flying_bishop,
    'direction'=> 's',
    'team' => 1,
    'graphic'   => 'some.gif',
);

$board->add(
    'b4',
    'gamepiece' => $flying_bishop,
    'direction'=> 'n',
    'team' => 0,
    'graphic'   => 'some.gif',
);

$board->add(
    'c4',
    'gamepiece' => $flying_bishop,
    'direction'=> 's',
    'team' => 1,
    'graphic'   => 'some.gif',
);

$board->add(
    'd4',
    'gamepiece' => $base,
    'direction'=> 's',
    'team' => 1,
    'graphic'   => 'some.gif',
);

## A:a1
# a1> legal
# a1+a2 legal

my @actions;
@legal_actions = $board->legal_actions( 'a1', 0 );
$first_action = $legal_actions[0];  # On-spot rotation

my @possible_directions = $first_action->possible_directions();
is( @possible_directions, 3, "[rotations] a1>: array size" );

foreach my $direction ( 'e', 's', 'w' ) {
    ok(  ( firstidx { $_ eq $direction } @possible_directions ) != -1,
         "[rotations] a1>: '$direction' allowed" );
}
ok(  ( firstidx { $_ eq 'n' } @possible_directions ) == -1,
     "[rotations] a1>: 'n' not allowed" );

@actions = grep { $_->to_string( 1 ) eq 'a1+a2' } @legal_actions;
is( @actions, 1, "[rotations] a1+a2 legal" );

@possible_directions = $actions[0]->possible_directions();
is( @possible_directions, 4, "[rotations] a1+a2>: array size" );

foreach my $direction ( 'n', 'e', 's', 'w' ) {
    ok(  ( firstidx { $_ eq $direction } @possible_directions ) != -1,
         "[rotations] a1+a2>: '$direction' allowed" );
}

dies_ok { $actions[0]->link_destination( 'b4', 'n' ) } '[link-destination] exception, not a carrier';

## B:b3
# b3-c3 is illegal
# b3+a2
# b3-a3>w : flier hits a boundary # TODO:

@legal_actions = $board->legal_actions( 'b3', 0 );
@actions = grep { $_->to_string( 1 ) eq 'b3-c3' } @legal_actions;
is( @actions, 1, "[rotations] b3-c3 legal" );

@possible_directions = $actions[0]->possible_directions();
is( @possible_directions, 2, "[rotations] b3-c3>: array size" );

foreach my $direction ( 's', 'w' ) {
    ok(  ( firstidx { $_ eq $direction } @possible_directions ) != -1,
         "[rotations] b3-c3>: '$direction' allowed" );
}
foreach my $direction ( 'n', 'e' ) {
    ok(  ( firstidx { $_ eq $direction } @possible_directions ) == -1,
         "[rotations] b3-c3>: '$direction' allowed" );
}

dies_ok { $actions[0]->link_destination( 'b4', 'n' ) } '[link-destination] exception, destination blocked';

@actions = grep { $_->to_string( 1 ) eq 'b3+a2' } @legal_actions;
is( @actions, 1, "[rotations] b3+a2 legal" );

@possible_directions = $actions[0]->possible_directions();
is( @possible_directions, 4, "[rotations] b3+a2>: array size" );

foreach my $direction ( 'n', 'e', 's', 'w' ) {
    ok(  ( firstidx { $_ eq $direction } @possible_directions ) != -1,
         "[rotations] b3+a2>: '$direction' allowed" );
}

my $destination = $actions[0]->link_destination( 'b4', 'w' );
is( $destination, 'a2', '[link-destination] b3+a2>w' );

dies_ok { $actions[0]->link_destination( 'c1', 'w' ) } '[link-destination] exception, unknown link';
dies_ok { $actions[0]->link_destination( 'a3', 'w' ) } '[link-destination] exception, wrong team';

## A: a1
# a1+b2 legal

@legal_actions = $board->legal_actions( 'a1', 0 );
@actions = grep { $_->to_string( 1 ) eq 'a1+a2' } @legal_actions;
is( @actions, 1, "[rotations] a1+a2 legal" );

@possible_directions = $actions[0]->possible_directions();
is( @possible_directions, 4, "[rotations] a1+a2>: array size" );

foreach my $direction ( 'n', 'e', 's', 'w' ) {
    my $i = firstidx { $_ eq $direction } @possible_directions;
    ok( $i != -1, "[rotations] b3+a2>: '$direction' allowed" );
}

dies_ok { $actions[0]->link_destination( 'b4', 'n' ) } '[link-destination] exception, not a carrier';
